﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Windows.Media.Imaging;
using AutoLoL.Entities;
using AutoLoL.Logic.Parsers;

namespace AutoLoL.Logic.Repositories
{
    public class RafItemRepository : IItemRepository
    {
        private ReadOnlyCollection<Item> _items;

        public Item GetByItemId(int itemId)
        {
            if (itemId > 0)
            {
                return GetAll().Where(c => c.ItemId == itemId).SingleOrDefault();
            }

            return null;
        }

        public ReadOnlyCollection<Item> GetAll()
        {
            if (_items != null)
            {
                return _items;
            }

            List<Item> items = new List<Item>();
            Dictionary<Item, List<int>> buildsIntoMapping = new Dictionary<Item, List<int>>();
            Dictionary<Item, List<int>> builtFromMapping = new Dictionary<Item, List<int>>();

            Func<string, bool> filter = ((path) =>
                path.ToLower().StartsWith("data/items")
                && path.ToLower().EndsWith(".inibin"));

            ReadOnlyCollection<RafFile> rafFiles = RafManager.Instance.GetFiles(filter);

            foreach (RafFile rafFile in rafFiles)
            {
                RafManager.Instance.SetRafFileData(rafFile);
                Dictionary<uint, InibinEntry> data = InibinParser.ParseBytes(rafFile.Data);

                if (data.ContainsKey((uint)InibinKeys.Item.IS_VALID) && data[(uint)InibinKeys.Item.IS_VALID].GetBit())
                {
                    // TODO - Added try catch to quick-fix Talon patch
                    try
                    {
                        string fckName = data[(uint)InibinKeys.Item.FCK_NAME].GetString();
                        string fckDescription = data[(uint)InibinKeys.Item.FCK_DESCRIPTION].GetString();

                        int id = int.Parse(fckDescription.Substring(22));
                        string name = FontConfigParser.Instance.GetValue(fckName);
                        string description = FontConfigParser.Instance.GetValue(fckDescription);

                        int price = int.Parse(data[(uint)InibinKeys.Item.INT_PRICE].ToString());

                        string iconName = data[(uint)InibinKeys.Item.PATH_ICON_DDS].GetString();
                        string iconPath = string.Format("data/items/icons2d/{0}", iconName);
                        RafFile iconRafFile = RafManager.Instance.GetFile(iconPath);
                        RafManager.Instance.SetRafFileData(iconRafFile);

                        BitmapSource iconBitmap = null;
                        if (iconName.ToLower().EndsWith(".dds"))
                        {
                            iconBitmap = ImageParser.DdsToBitmapSource(iconRafFile.Data);
                        }
                        else if (iconName.ToLower().EndsWith(".tga"))
                        {
                            iconBitmap = ImageParser.TgaToBitmapSource(iconRafFile.Data);
                        }

                        if (name != null && description != null && iconBitmap != null)
                        {
                            Item item = new Item();
                            item.ItemId = id;
                            item.Name = name;
                            item.Description = description;
                            item.Price = price;
                            item.IconBitmapImage = iconBitmap;
                            items.Add(item);

                            #region Builds Into and Built From
                            for (int i = 0; i < InibinKeys.ITEM_BUILDS_INTO_KEYS.Length; i++)
                            {
                                if (data.ContainsKey(InibinKeys.ITEM_BUILDS_INTO_KEYS[i]))
                                {
                                    int buildsIntoId = int.Parse(data[InibinKeys.ITEM_BUILDS_INTO_KEYS[i]].ToString());
                                    if (buildsIntoId > 0)
                                    {
                                        if (!buildsIntoMapping.ContainsKey(item))
                                        {
                                            buildsIntoMapping.Add(item, new List<int>());
                                        }

                                        buildsIntoMapping[item].Add(buildsIntoId);
                                    }
                                }
                            }

                            for (int i = 0; i < InibinKeys.ITEM_BUILT_FROM_KEYS.Length; i++)
                            {
                                if (data.ContainsKey(InibinKeys.ITEM_BUILT_FROM_KEYS[i]))
                                {
                                    int builtFromId = int.Parse(data[InibinKeys.ITEM_BUILT_FROM_KEYS[i]].ToString());
                                    if (builtFromId != 0)
                                    {
                                        if (!builtFromMapping.ContainsKey(item))
                                        {
                                            builtFromMapping.Add(item, new List<int>());
                                        }

                                        builtFromMapping[item].Add(builtFromId);
                                    }
                                }
                            }
                            #endregion
                        }
                    }
                    catch { if (Debugger.IsAttached) throw; }
                }
            }

            SetBuildsInto(items, buildsIntoMapping);
            SetBuiltFrom(items, builtFromMapping);
            SetItemTotals(items);

            items.Sort((x1, x2) => x1.Name.CompareTo(x2.Name));

            _items = new ReadOnlyCollection<Item>(items);

            return _items;
        }

        private void SetItemTotals(List<Item> items)
        {
            // Reset all totals
            foreach (Item item in items)
            {
                item.Total = -1;
            }

            // Set all basic items
            foreach (Item item in items.Where(c => c.BuiltFrom == null))
            {
                item.Total = item.Price;
            }

            int iterations = 0;
            const int maxIterations = 100;
            IEnumerable<Item> queue = null;

            while ((queue == null || queue.Any()) && iterations < maxIterations)
            {
                // Find items without a Total
                queue = items.Where(c => c.Total == -1 && c.BuiltFrom != null && c.BuiltFrom.Length > 0);

                foreach (Item item in queue)
                {
                    //  Calculate the total or skip untill the next iteration when failed
                    int total = 0;
                    bool failed = false;

                    foreach (Item builtFrom in item.BuiltFrom)
                    {
                        if (builtFrom.Total != -1)
                        {
                            total += builtFrom.Total;
                        }
                        else
                        {
                            failed = true;
                            break;
                        }
                    }

                    // Set the total
                    if (!failed)
                    {
                        item.Total = (total + item.Price);
                    }
                }

                iterations++;
            }
        }

        private void SetBuildsInto(List<Item> items, Dictionary<Item, List<int>> buildsIntoMapping)
        {
            foreach (Item item in buildsIntoMapping.Keys)
            {
                List<Item> buildsIntoItems = new List<Item>();

                foreach (int itemId in buildsIntoMapping[item])
                {
                    Item buildsInto = items.Where(c => c.ItemId == itemId).SingleOrDefault();
                    if (buildsInto != null)
                    {
                        buildsIntoItems.Add(buildsInto);
                    }
                }

                item.BuildsInto = buildsIntoItems.ToArray();
            }
        }

        private void SetBuiltFrom(List<Item> items, Dictionary<Item, List<int>> builtFromMapping)
        {
            foreach (Item item in builtFromMapping.Keys)
            {
                List<Item> builtFromItems = new List<Item>();

                foreach (int itemId in builtFromMapping[item])
                {
                    Item builtFrom = items.Where(c => c.ItemId == itemId).SingleOrDefault();
                    if (builtFrom != null)
                    {
                        builtFromItems.Add(builtFrom);
                    }
                }

                item.BuiltFrom = builtFromItems.ToArray();
            }
        }
    }
}
